﻿namespace BM.Tools.Xml.Schema
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Xml.Schema;

    /// <summary>
    /// A helper class that takes various traverse methods and runs only the one with matching type.
    /// </summary>
    /// <typeparam name="TVisited">
    /// the type of the visited object.
    /// </typeparam>
    public class TraverseFork<TVisited> where TVisited : class, IVisited
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="TraverseFork{TVisited}"/> class.
        /// </summary>
        /// <param name="schemaObject">
        /// The schema object.
        /// </param>
        /// <param name="visited">
        /// The visited.
        /// </param>
        public TraverseFork(XmlSchemaObject schemaObject, TVisited visited)
        {
            SchemaObject = schemaObject;
            Visited = visited;
            TraverseMethods = new List<Func<bool>>();
        }

        /// <summary>
        /// A definition of xml schema object traverse method
        /// </summary>
        /// <param name="schemaObject">
        /// The schema object.
        /// </param>
        /// <param name="visited">
        /// The visited object.
        /// </param>
        /// <typeparam name="TSchemaObject">
        /// the type of the xml schema object
        /// </typeparam>
        public delegate void TraverseSchemaObject<TSchemaObject>(TSchemaObject schemaObject, TVisited visited);

        /// <summary>
        /// Gets the schema object that will be passed to the traverse method.
        /// </summary>
        public XmlSchemaObject SchemaObject
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the visited object that will be passed to the traverse method.
        /// </summary>
        public TVisited Visited
        {
            get;
            private set;
        }

        private List<Func<bool>> TraverseMethods
        {
            get;
            set;
        }

        /// <summary>
        /// Adds the traverse method definition to the fork
        /// </summary>
        /// <param name="traverseMethod">
        /// The traverse method.
        /// </param>
        /// <typeparam name="TSchemaObject">
        /// the type of the schema object for which the method should run.
        /// </typeparam>
        public void AddTraverseMethod<TSchemaObject>(TraverseSchemaObject<TSchemaObject> traverseMethod)
            where TSchemaObject : class
        {
            TraverseMethods.Add(() => TraverseIfSchemaObjectMatches(traverseMethod, SchemaObject as TSchemaObject, Visited));
        }

        /// <summary>
        /// Runs the first method for which the type of the schema object matches
        /// </summary>
        public void Run()
        {
            TraverseMethods.Any(tryTraverse => tryTraverse());            
        }

        private static bool TraverseIfSchemaObjectMatches<TSchemaObject>(TraverseSchemaObject<TSchemaObject> traverse, TSchemaObject schemaObject, TVisited parent) where TSchemaObject : class
        {
            if (schemaObject != null)
            {
                traverse(schemaObject, parent);
                return true;
            }

            return false;
        }
    }
}
